package nachos.threads;

import nachos.machine.*;

/**
 * A <i>communicator</i> allows threads to synchronously exchange 32-bit
 * messages. Multiple threads can be waiting to <i>speak</i>, and multiple
 * threads can be waiting to <i>listen</i>. But there should never be a time
 * when both a speaker and a listener are waiting, because the two threads can
 * be paired off at this point.
 */
public class Communicator
{
	/**
	 * Allocate a new communicator.
	 */
	public Communicator()
	{
		messageSending = false;
		numSpeakers = 0;
		numListeners = 0;
		message = 0;
		lock = new Lock();
		pairCond = new Condition2(lock);
		messageSpokenCond = new Condition2(lock);
		messageReadCond = new Condition2(lock);
	}

	/**
	 * Wait for a thread to listen through this communicator, and then transfer
	 * <i>word</i> to the listener.
	 * 
	 * <p>
	 * Does not return until this thread is paired up with a listening thread.
	 * Exactly one listener should receive <i>word</i>.
	 * 
	 * @param word
	 *            the integer to transfer.
	 */
	public void speak(int word)
	{
		lock.acquire();
		if (numListeners == 0)
		{
			numSpeakers++;
			pairCond.sleep();
		} else
		{
			numListeners--;
			pairCond.wake();
		}
		if (messageSending)
			messageReadCond.sleep();
		message = word;
		messageSending = true;
		messageSpokenCond.wake();
		lock.release();
	}

	/**
	 * Wait for a thread to speak through this communicator, and then return the
	 * <i>word</i> that thread passed to <tt>speak()</tt>.
	 * 
	 * @return the integer transferred.
	 */
	public int listen()
	{
		int word;
		lock.acquire();
		if (numSpeakers == 0)
		{
			numListeners++;
			pairCond.sleep();
		} else
		{
			numSpeakers--;
			pairCond.wake();
		}
		if (!messageSending)
			messageSpokenCond.sleep();
		word = message;
		messageSending = false;
		messageReadCond.wake();
		lock.release();
		return word;
	}

	private boolean		messageSending;
	private int			numSpeakers;
	private int			numListeners;
	private int			message;
	private Lock		lock;
	private Condition2	pairCond;
	private Condition2	messageSpokenCond;
	private Condition2	messageReadCond;
}
